package org.simtube.index;

import java.util.Iterator;
import java.util.List;

import org.simtube.util.DocVector;

/**
 * A Region object represents a spatial region to index. Here in
 * SimTube, a Region is defined as a circle on a sphere. However,
 * this definition may be updated and expanded.
 * 
 * A presumption here is that all center vector should be of unit
 * length.
 * 
 * @author Li Yujia
 *
 */
public class Region {
	
	private DocVector center;
	private double angle;
	
	// setters and getters
	
	public void setAngle(double angle) {
		this.angle = angle;
	}

	public double getAngle() {
		return angle;
	}

	public void setCenter(DocVector center) {
		this.center = center;
	}

	public DocVector getCenter() {
		return center;
	}

	/**
	 * The constructor. A center and an angle should be specified.
	 * 
	 * @param c The center vector.
	 * @param a The angle (range of the region)
	 */
	public Region(DocVector c, double a) {
		setCenter(c);
		setAngle(a);
	}
	
	/**
	 * Calculate the distance of two regions
	 * 
	 * @param r1 The first region
	 * @param r2 The second region
	 * @return The distance
	 */
	public static double distance(Region r1, Region r2) {
		return (Math.acos(r1.getCenter().multiply(r2.getCenter()))
				- r1.getAngle() - r2.getAngle());
	}
	
	/**
	 * Calculate the distance of the two centers of the two regions
	 * 
	 * @param r1 The first region
	 * @param r2 The second region
	 * @return The distance
	 */
	public static double centerDistance(Region r1, Region r2) {
		return (Math.acos(r1.getCenter().multiply(r2.getCenter())));
	}
	
	/**
	 * Calculate the maximum distance between the two regions
	 * 
	 * @param r1 The first region
	 * @param r2 The second region
	 * @return The distance
	 */
	public static double maxDistance(Region r1, Region r2) {
		return (Math.acos(r1.getCenter().multiply(r2.getCenter()))
				+ r1.getAngle() + r2.getAngle());
	}
	
	/**
	 * Judge whether two regions will overlap or not
	 * 
	 * @param r1 The first region
	 * @param r2 The second region
	 * @return True if they do overlap, false otherwise
	 */
	public static boolean isOverlap(Region r1, Region r2) {
		return (distance(r1, r2) < 0);
	}
	
	/**
	 * Find the minimal region that will cover all the regions in the list,
	 * here for the sake of efficiency, the region may not be optimal but
	 * near optimal.
	 * 
	 * @param rlist A list of regions that will be covered
	 * @return The minimal covering region
	 */
	public static Region minCoverRegion(Region[] rlist) {
		
		DocVector c = null;
		for(int i = 0; i < rlist.length; i++) {
			if(i == 0)
				c = rlist[i].getCenter();
			else
				c = c.add(rlist[i].getCenter());
		}
		
		if(c != null) {
			c = c.multiply(1.0 / rlist.length).normalize();
		}
		
		if(c == null) {
			System.err.println("Empty rlist in Region.java!");
			throw new NullPointerException();
		}
		
		double angle = 0;
		double ta = 0;
		
		for(int i = 0; i < rlist.length; i++) {
			ta = Math.acos(c.multiply(rlist[i].getCenter()))
				+ rlist[i].getAngle();
			if(ta > angle) angle = ta;
		}
		
		return new Region(c, angle);
	}
	
	/**
	 * Find the minimal region that will cover all the regions in the list,
	 * here for the sake of efficiency, the region may not be optimal but
	 * near optimal.
	 * 
	 * @param rlist A list of regions that will be covered
	 * @return The minimal covering region
	 */
	public static Region minCoverRegion(List<Region> rlist) {
		return minCoverRegion(rlist.toArray(new Region[0]));
	}
	
	/**
	 * Find the minimal region that will cover all the regions in the node,
	 * here for the sake of efficiency, the region may not be optimal but
	 * near optimal.
	 * 
	 * @param n The node that will be covered
	 * @return The minimal covering region
	 */
	public static Region minCoverRegion(CNode n) {
		Region[] rlist = new Region[n.size()];
		Iterator<CEntry> it = n.getElist().iterator();
		int i = 0;
		while(it.hasNext()) {
			rlist[i++] = it.next().getRegion();
		}
		return minCoverRegion(rlist);
	}
	
	public String toString() {
		return "Center: " + center + ", angle = " + angle;
	}
	
	/**
	 * Get the dimension sum of the center vector
	 * 
	 * @return The dimension sum
	 */
	public double dimSum() {
		return center.dimSum();
	}
	
	public static void main(String[] args) {
		double[] vv1 = new double[2];
		vv1[0] = 1; vv1[1] = 0;
		double[] vv2 = new double[2];
		vv2[0] = 0; vv2[1] = 1;
		
		Region[] r = new Region[2];
		r[0] = new Region(new DocVector(vv1), 1);
		r[1] = new Region(new DocVector(vv2), 1);
		
		System.out.println(Region.minCoverRegion(r));
	}
}
